Repository navigation
fix(libc): keep buffered input when a seek target is rejected - #15236
AlexWhitehouse wants to merge 1 commit into
Conversation
Check fseeko targets before the host C library sees them, as FreeBSD 11.4's _fseeko does: a negative SEEK_SET, SEEK_CUR or regular-file SEEK_END target fails with EINVAL and one past OFF_MAX with EOVERFLOW, leaving the buffer, pushback and EOF state alone. UCRT dropped the read buffer before rejecting such a target, so the stream continued at the descriptor's read-ahead offset; glibc dropped pending ungetc data and returned EINVAL for an overflowing target. SEEK_END on a stream in write mode or on a nonregular file still goes to the host library, since finding its end would need a flush. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent Linux tester run of head
AI-assisted: yes |
|
A Windows run with GCC 16: Windows 10.0.22631 (Windows 11), i9-9900K, MSYS2 UCRT64 GCC 16.1.0 (Rev5), Release, this PR (
The log doesn't say which of the five seek forms hit line 71 first, and the test stops there 👍 AI-assisted: yes (Claude Code) |
|
Thanks for the run. On UCRT the first form, the rejected |
|
Windows check of head Setup: Windows 11 Enterprise 22621, WinLibs GCC 15.2 UCRT, Release. The hosted Windows check does not run this test on pull requests.
AI-assisted: yes. |
What
fseeko(core/libs/prx/libc/src/FileAndHeap.cpp;fseekandfsetposcall it) handed every seek straight to the host C library. When the target was invalid, both hosts changed the stream before failing:_fseeki64drops the read buffer before itslseekto a negative target fails withEINVAL, so the stream continued at the descriptor's read-ahead offset. After reading 4 of 8 buffered bytes, a rejectedfsetposto -1 leftftelloat 8 and the next read at end of file.docs/dev/debt/libc.mdrecorded this.ungetcdata on the failure path and returnsEINVALinstead ofEOVERFLOWfor a target pastOFF_MAX.FreeBSD 11.4's
_fseekochecks the target before touching the stream, and returns -1 with the buffer, pushback and EOF state unchanged when the check fails.This PR adds a
ResolveSeekTargethelper that does the same before either host library sees the call:SEEK_CUR: the base is the stream'sftelloposition. A failure there is returned as is, and a negative base isESPIPE.SEEK_END: the base is the file size fromfstat, but only for a regular file whose stream is not in write mode (UCRT's write flag on Windows,__fwritingon glibc). Buffered output would make the logical end larger, and finding it would need a flush. On UCRT, a flush on a read stream discards the buffer, which is the bug.OFF_MAXisEOVERFLOW(84); a negative target isEINVAL(22). A valid target is passed on asSEEK_SET.The Windows pipe check and the closed-stream and origin checks still come first. The success path is unchanged.
The debt entry now leads with what remains:
SEEK_ENDon a stream in write mode, on a non-regular file, or on macOS still goes to the host library;Tested
file_position(core/libs/tests/FilePosition.cpp) gains two cases. Every existing assertion is kept.fsetposto -1,fseeko(-1, SEEK_SET),fseek(-5, SEEK_CUR),fseeko(-9, SEEK_END)andfseeko(INT64_MAX, SEEK_CUR). Each must fail withEINVAL(orEOVERFLOWfor the last), leaveftelloat 4, and leave EOF and error unchanged. A pushed-back byte must survive a rejectedSEEK_CURand a rejectedSEEK_SET, and the next reads must return the original bytes.SEEK_ENDwith pending output: still goes to the host and lands where expected.main'sFileAndHeap.cpp,file_positionfails at line 70 (EINVALinstead ofEOVERFLOW);file_position,file_streams,file_stream_status,guest_standard_streams*,guest_stdio_variadic,guest_wide_stream_scan,guest_stream_purge,guest_fopen_errors) passes 12/12.FileAndHeap.cppfrommain(9090972),file_positionfails at line 71:ftellois 8, not 4, after the rejectedfsetpos;guest_fopen_long_host_pathandguest_stream_buffer_host_writepasses 13/13, withguest_fopen_errorsskipped for lack of symlink rights;file_positionfail at line 86 on Linux, because glibc drops the pushed-back byte. On Windows, removing theSEEK_SETcheck from the first, Windows-only version of the change failed at theftellocheck after the rejectedfsetpos.python3 tools/check_conventions.py --base origin/main: all conventions checks passed.python3 tools/pr_overlap.py: no confirmed overlap. It could not compare 27 PRs that already conflict withmain, so their changes were checked by hand: theFileAndHeap.cpphunks of the eight that edit it, and thelibc.mdchanges of all 27.const int nativeError = errno;infseeko. This PR leaves that line and the::fseekoline alone.fwrite, three lines above the new helper.<sys/stat.h>include higher in the include block. If both merge, the include appears twice, which is harmless.fopen,freopen,freadand_Fiopen.fseeko and fsetposdebt entry.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)🤖 Generated with Claude Code